package xyz.scootaloo.kami.server.service.impl

import io.vertx.core.Future
import io.vertx.core.Promise
import io.vertx.kotlin.coroutines.await
import xyz.scootaloo.kami.server.model.AccessLevel
import xyz.scootaloo.kami.server.model.AccessToken
import xyz.scootaloo.kami.server.model.AccessType.*
import xyz.scootaloo.kami.server.model.dao.LimitDAO
import xyz.scootaloo.kami.server.model.dao.RuleDAO
import xyz.scootaloo.kami.server.model.plus
import xyz.scootaloo.kami.server.standard.getLogger
import xyz.scootaloo.kami.server.service.*
import xyz.scootaloo.kami.server.service.RuleSystemService.LowRuleItem
import xyz.scootaloo.kami.server.service.RuleSystemService.RuleItem
import java.util.*

/**
 * 权限校验逻辑内部实现
 *
 * @author flutterdash@qq.com
 * @since 2022/2/8 10:48
 */
object InternalRuleSystemServiceImpl : RuleSystemService, ApplicationStageListener {

    private val log by lazy { getLogger() }

    private val fileLogService = FileLogService()

    private val FAKE_ADMIN = AccessToken("", AccessLevel.SUPER_ADMIN)

    override fun afterDatabaseAvailable() {
        val rules = RuleDAO.list()
        val limits = LimitDAO.list()
        log.info("加载规则${rules.size + limits.size}个")
        AccessControl.modifySystemModel {
            rules.forEach { VirtualFileSystem.editor.insertByDatabaseRule(it) }
            limits.forEach { VirtualFileSystem.editor.insertByDatabaseLimit(it) }
        }
    }

    override fun <T> access(token: AccessToken, block: (RuleItem) -> T): Future<T> {
        return AccessControl.acquire().compose {
            try {
                val ruleNode = VirtualFileSystem.access(token)
                val rtnValue = block(ruleNode)
                Future.succeededFuture(rtnValue)
            } catch (err: Throwable) {
                Future.failedFuture(err)
            } finally {
                AccessControl.release()
            }
        }
    }

    override fun <T> accessAsync(token: AccessToken, block: (RuleItem) -> Future<T>): Future<T> {
        return AccessControl.acquire().compose {
            try {
                val ruleNode = VirtualFileSystem.access(token)
                block(ruleNode).onComplete {
                    AccessControl.release()
                }
            } catch (err: Throwable) {
                Future.failedFuture(err)
            }
        }
    }

    override suspend fun <T> accessWithCoroutine(
        token: AccessToken, block: suspend (RuleItem) -> T
    ): T {
        AccessControl.acquire().await()
        return try {
            val ruleNode = VirtualFileSystem.access(token)
            block(ruleNode)
        } catch (err: Throwable) {
            throw err
        } finally {
            AccessControl.release()
        }
    }

    override fun tryShrinkageCap(path: String, amount: Long): Future<Unit> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.editor.shrinkageCapacity(path, amount) { path, limit, free ->
                fileLogService.onCapacityChange(path, limit, free)
            }
        }
    }

    override fun releaseCap(path: String, amount: Long): Future<Unit> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.editor.releaseCap(path, amount) { path, limit, free ->
                fileLogService.onCapacityChange(path, limit, free)
            }
        }
    }

    override fun createRule(token: AccessToken, rule: ByteArray): Future<Unit> {
        return updateRule(token, rule)
    }

    override fun updateRule(token: AccessToken, rule: ByteArray): Future<Unit> {
        return access(token + DEFINE).compose {
            AccessControl.modifySystemModel {
                val duplicate = VirtualFileSystem.editor.insertRule(token.path, rule).rule.clone()
                Async.run { RuleDAO.store(token.path, duplicate) }
            }
        }
    }

    override fun deleteRule(token: AccessToken): Future<Unit> {
        return access((token + DEFINE)).compose {
            AccessControl.modifySystemModel {
                VirtualFileSystem.editor.deleteRule(token.path)
                Async.run { RuleDAO.delete(token.path) }
            }
        }
    }

    override fun showRule(path: String): Future<LowRuleItem> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.viewer.getRuleNode(path)
        }
    }

    override fun showCap(path: String): Future<LowRuleItem> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.viewer.getCapNode(path)
        }
    }

    override fun deleteLimit(token: AccessToken): Future<Unit> {
        return access((token + DEFINE)).compose {
            AccessControl.modifySystemModel {
                VirtualFileSystem.editor.deleteLimit(token.path)
                Async.run { LimitDAO.delete(token.path) }
            }
        }
    }

    override fun createCapLimit(token: AccessToken, limit: Long): Future<Unit> {
        return access((token + DEFINE)).compose {
            AccessControl.modifySystemModel {
                LimitDAO.store(token.path, limit)
                VirtualFileSystem.editor.insertLimit(token.path, limit)
            }
        }
    }

    override fun updateCapAvailable(path: String, used: Long): Future<Unit> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.editor.updateCapAvailable(path, used)
        }
    }

    override suspend fun listCapLimitPaths(): Future<List<String>> {
        return access(FAKE_ADMIN) {
            VirtualFileSystem.viewer.listCapLimitPaths()
        }
    }

    object AccessControl {

        private var usingCount = 0
        private val taskQueue: Queue<UpdateTask> = LinkedList()

        fun acquire(): Future<Unit> {
            return Sync.run {
                usingCount++
            }
        }

        fun release(): Future<Unit> {
            return Sync.run {
                usingCount--
                if (usingCount == 0) {
                    checkUpdateTasks()
                }
            }
        }

        /**
         * 会对规则系统结构产生变动的操作, 调用此方法;
         * 节点的创建, 删除;
         */
        fun modifySystemModel(command: () -> Unit): Future<Unit> {
            return Sync { promise ->
                tryUpdate(promise, command)
            }
        }

        private fun tryUpdate(promise: Promise<Unit>, command: Runnable) {
            val task = wrapInUpdateTask(promise, command)
            if (!task.tryUpdate()) {
                taskQueue.add(task)
            }
        }

        private fun checkUpdateTasks() {
            if (taskQueue.isEmpty())
                return
            while (taskQueue.isNotEmpty()) {
                val earliest = taskQueue.remove()
                earliest.tryUpdate()
            }
        }

        private fun wrapInUpdateTask(
            promise: Promise<Unit>, task: Runnable
        ): UpdateTask {
            return UpdateTask(promise, task)
        }

        class UpdateTask(
            private val promise: Promise<Unit>,
            private val command: Runnable
        ) {
            fun tryUpdate(): Boolean {
                if (usingCount != 0)
                    return false
                safeUpdate()
                return true
            }

            private fun safeUpdate() = try {
                command.run()
                promise.complete()
            } catch (e: Throwable) {
                log.warn("更新规则树时遇到异常: ${e.message}", e)
                promise.fail(e)
            }
        }
    }
}